

package controle;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
 

public class Maquina_Virtual {
    ArrayList prog;
    Pilha pilha;
    int ip; // instruction pointer
    int topo; // stack pointer
    
    boolean halt; // paradas por runtime error
    

    public Maquina_Virtual(String[] inputProgram, String _param) {
        super();
        halt = false;
        
        
        prog = new ArrayList();
        pilha = new Pilha();
        ip = 1;
        topo = pilha.getSp();
        
        prog.add("");
        for(int i = 0; i< inputProgram.length; i++){
            prog.add(inputProgram[i]);
        }
        
        
        //termina o debug aqui
        while (true){
            
            if (_param.equals("debug")){
                System.out.println(prog.get(ip).toString());
            }
            String[] buff = prog.get(ip).toString().split(" ");
            int i = 3;
            
            while (true){
                try{
                buff[2] += " "+buff[i];
                i++;
                }catch(ArrayIndexOutOfBoundsException e){
                break;
            }
            }
                 
            executaInstrucao(buff[0],buff[1],buff[2]);
        }
        
    }
    
    
    
    
    private void add(){
        String base = pilha.pop().toString(); // pega o topo da pilha
        String soma = pilha.pop().toString();
        if (testeCompatTipos(base, soma)){
            int tipo = retornaTipo(base);
                switch (tipo){
                    case 1:
                            pilha.push(String.valueOf(Integer.parseInt(base)+Integer.parseInt(soma)));
                            break;
                    case 2:
                            pilha.push(Double.parseDouble(base)+Double.parseDouble(soma));
                            break;
                    case 3:
                            pilha.push(base+soma);
                            break;
                }
        }else{
            halt("\n Runtime error: O programa tentou somar dois tipos incompativeis. \n");
        }
        ip++; // aponta para a pr�xima instru��o
    }
    
    private void div(){
        String base = pilha.pop().toString(); // pega o topo da pilha
        String divisao = pilha.pop().toString();  // divisao /base
        if (testeCompatTipos(base, divisao)){
            int tipo = retornaTipo(base);
            switch (tipo){
                case 1: 
                        if (Integer.parseInt(base) == 0){
                            halt("RUNTIME error: divisao por 0");
                            halt = true;
                        }else{
                            pilha.push(new String (String.valueOf(Integer.parseInt(divisao)/Integer.parseInt(base))));
                        }
                        break;
                case 2:
                        if (Double.parseDouble(base) == 0){
                            halt("RUNTIME error: divisao por 0");
                            halt = true;
                        }else{
                            pilha.push(new String (String.valueOf(Double.parseDouble(divisao)/Double.parseDouble(base))));
                        }
                        break;
            }
        }else{
            halt("\n Runtime error: O programa tentou Dividir dois tipos incompativeis. \n");
        }

        ip++;
    }

    private void mul(){
        String base = pilha.pop().toString();
        String multiplicacao = pilha.pop().toString();
        if (testeCompatTipos(base, multiplicacao)){
            int tipo = retornaTipo(base);
            switch (tipo){
                case 1 :    pilha.push(String.valueOf(Integer.parseInt(base)*Integer.parseInt(multiplicacao)));
                            break;
                case 2 :    pilha.push(String.valueOf(Double.parseDouble(base)*Double.parseDouble(multiplicacao)));
                            break;
            }
            
        }else{
             halt("\n Runtime error: O programa tentou multiplicar dois tipos incompativeis. \n");
        }
        
        ip++;       
    }
    
    private void sub(){
        String base = pilha.pop().toString(); // pega o topo da pilha
        String soma = pilha.pop().toString();
        if (testeCompatTipos(base, soma)){
            int tipo = retornaTipo(base);
                switch (tipo){
                    case 1:
                            pilha.push(String.valueOf(Integer.parseInt(base)-Integer.parseInt(soma)));
                            break;
                    case 2:
                            pilha.push(Double.parseDouble(base)-Double.parseDouble(soma));
                            break;
                }
        }else{
            halt("\n Runtime error: O programa tentou subtrair dois tipos incompativeis. \n");
        }
        ip++; // aponta para a pr�xima instru��o
    }
    
    private void alb(int deslocamento){
        topo = pilha.getSp();
        for (int i= topo+1; i <= topo+deslocamento; i++){
            pilha.push("FALSE");
        }
       ip++;
    }
    
    private void ali(int deslocamento){
        topo = pilha.getSp();
        for (int i= topo+1; i <= topo+deslocamento; i++){
            pilha.push("0");
        }
       ip++; 
    }
    
    private void alr(int deslocamento){
      topo = pilha.getSp();
        for (int i= topo+1; i <= topo+deslocamento; i++){
            pilha.push("0.0");
        }
       ip++;   
    }
    
    private void als(int deslocamento){
        topo = pilha.getSp();
        for (int i= topo+1; i <= topo+deslocamento; i++){
            pilha.push("");
        }
       ip++;
    }
    
   private void ldb(String const_bool){
       if (retornaTipo(const_bool)==4){
            pilha.push(String.valueOf(const_bool));
       }else{
           halt("Runtime error: Tipo prescisa ser booleano");
       }
       ip++;        
   }
   
   private void ldi(String const_int){
       if (retornaTipo(const_int)==1){
            pilha.push(String.valueOf(const_int));
       }else{
           halt("Runtime error: Tipo prescisa set inteiro");
       }
       ip++;        
   }
   
   private void ldr(String const_real){
       if (retornaTipo(const_real)==2 | retornaTipo(const_real)==1){
            if (retornaTipo(const_real)==1){
                pilha.push(String.valueOf(Double.parseDouble(const_real)));
            }else{
                pilha.push(String.valueOf(const_real));
            }
            
       }else{
           halt("Runtime error: Tipo prescisa set Real");
       }
       ip++;         
   }
   
   private void lds(String constante_literal){
        if (retornaTipo(constante_literal)==3){
            pilha.push(new String(constante_literal.toString()));
        }else{
            halt("Runtime error: Tipo prescisa set Literal");
        }
        ip++;
   }
   
   
   private void ldv(int endereco){
       String topo = pilha.getlEmentAt(endereco).toString();
       pilha.push(topo);
       ip++;        
   }
   
   private void str(int endereco){
        String topo = pilha.getlEmentAt(pilha.getSp()).toString();
        pilha.replaceEmentAt(endereco, new String(topo));
        pilha.pop();
        ip++;
   }
   
   private void and(){
       boolean base = Boolean.parseBoolean(pilha.pop().toString());
       boolean comp = Boolean.parseBoolean(pilha.pop().toString());
       pilha.push(String.valueOf(base && comp));
       ip++;
   }
   
   private void not(){
        boolean base = Boolean.parseBoolean(pilha.pop().toString());
        pilha.push(String.valueOf(!base));
        ip++;
   }
   
   private void or(){
       boolean base = Boolean.parseBoolean(pilha.pop().toString());
       boolean comp = Boolean.parseBoolean(pilha.pop().toString());
       pilha.push(String.valueOf(base || comp));
       ip++;
   }
   
   private void bge(){
       String topo = pilha.pop().toString();
       String base = pilha.pop().toString();
       if (testeCompatTipos(topo, base)) {
          int tipo = retornaTipo(topo);
          switch (tipo){
              case 1: // inteiro
                    if (Integer.parseInt(topo)<=Integer.parseInt(base))
                        pilha.push(base);
                    break;
              case 2: // real
                    if (Double.parseDouble(topo)<=Double.parseDouble(base))
                        pilha.push(base);
                    break;
              case 3: // literal 
                    halt("\n Runtime error: Comparacao BGE nao permitida para literias. \n");
                    break;
              case 4: // booleano
                    halt("\n Runtime error: Comparacao BGE nao permitida para booleanos. \n");
                    break;
          }
       }else{
           halt("\n Runtime error: O programa tentou comparar dois tipos incompativeis. \n");
       }
       ip++;     
   }
   
   private void bgr(){
       String topo = pilha.pop().toString();
       String base = pilha.pop().toString();
       if (testeCompatTipos(topo, base)) {
          int tipo = retornaTipo(topo);
          switch (tipo){
              case 1: // inteiro
                    if (Integer.parseInt(topo)<Integer.parseInt(base))
                        pilha.push(base);
                    break;
              case 2: // real
                    if (Double.parseDouble(topo)<Double.parseDouble(base))
                        pilha.push(base);
                    break;
              case 3: // literal 
                    halt("\n Runtime error: Comparacao BGR nao permitida para literias. \n");
                    break;
              case 4: // booleano
                    halt("\n Runtime error: Comparacao BGR nao permitida para booleanos. \n");
                    break;
          }
       }else{
           halt("\n Runtime error: O programa tentou comparar dois tipos incompativeis. \n");
       }
       ip++;     
   }
   
   private void dif(){
       String topo = pilha.pop().toString();
       String base = pilha.pop().toString();
       if (testeCompatTipos(topo, base)) {
          int tipo = retornaTipo(topo);
          int tipo2 = retornaTipo(base);
          switch (tipo){
              case 1: // inteiro
                    if (Integer.parseInt(topo)!=Integer.parseInt(base))
                        pilha.push(base);
                    break;
              case 2: // real
                    if (Double.parseDouble(topo)!=Double.parseDouble(base))
                        pilha.push(base);
                    break;
              case 3: // literal 
                    if (!topo.equalsIgnoreCase(base))
                        pilha.push(base);
                    break;
              case 4: // booleano
                    if (Boolean.parseBoolean(topo)!=Boolean.parseBoolean(base))
                    pilha.push(base);
                    break;
          }
       }else{
           halt("\n Runtime error: O programa tentou comparar dois tipos incompativeis. \n");
       }
       ip++;     
   }
   
   private void eql(){
       String topo = pilha.pop().toString();
       String base = pilha.pop().toString();
       if (testeCompatTipos(topo, base)) {
          int tipo = retornaTipo(topo);
          switch (tipo){
              case 1: // inteiro
                    if (Integer.parseInt(topo)==Integer.parseInt(base))
                        pilha.push(base);
                    break;
              case 2: // real
                    if (Double.parseDouble(topo)==Double.parseDouble(base))
                        pilha.push(base);
                    break;
              case 3: // literal 
                    if (topo.equalsIgnoreCase(base))
                        pilha.push(base);
                    break;
              case 4: // booleano
                    if (Boolean.parseBoolean(topo)==Boolean.parseBoolean(base))
                    pilha.push(base);
                    break;
          }
       }else{
           halt("\n Runtime error: O programa tentou comparar dois tipos incompativeis. \n");
       }
       ip++;
   }
   
   private void sme(){

       String topo = pilha.pop().toString();
       String base = pilha.pop().toString();
       if (testeCompatTipos(topo, base)) {
          int tipo = retornaTipo(topo);
          switch (tipo){
              case 1: // inteiro
                    if (Integer.parseInt(topo)>=Integer.parseInt(base))
                        pilha.push(base);
                    break;
              case 2: // real
                    if (Double.parseDouble(topo)>=Double.parseDouble(base))
                        pilha.push(base);
                    break;
              case 3: // literal 
                    halt("\n Runtime error: Comparacao SME nao permitida para literias. \n");
                    break;
              case 4: // booleano
                    halt("\n Runtime error: Comparacao SME nao permitida para booleanos. \n");
                    break;
          }
       }else{
           halt("\n Runtime error: O programa tentou comparar dois tipos incompativeis. \n");
       }
       ip++;
   }
   
   private void smr(){
       String topo = pilha.pop().toString();
       String base = pilha.pop().toString();
       if (testeCompatTipos(topo, base)) {
          int tipo = retornaTipo(topo);
          switch (tipo){
              case 1: // inteiro
                    if (Integer.parseInt(topo)>Integer.parseInt(base))
                        pilha.push(base);
                    break;
              case 2: // real
                    if (Double.parseDouble(topo)>Double.parseDouble(base))
                        pilha.push(base);
                    break;
              case 3: // literal 
                    halt("\n Runtime error: Comparacao SMR nao permitida para literias. \n");
                    break;
              case 4: // booleano
                    halt("\n Runtime error: Comparacao SMR nao permitida para booleanos. \n");
                    break;
          }
       }else{
           halt("\n Runtime error: O programa tentou comparar dois tipos incompativeis. \n");
       }
       ip++;
   }
   
   private void jmf(int endereco){
        boolean topo = Boolean.parseBoolean(pilha.pop().toString());
        //pilha.push(String.valueOf(topo));
        if (topo == false){
            ip = endereco;
        }else{
            ip++;
        }
   }
   
   private void jmp(int endereco){
        ip = endereco;
   }
   
   private void jmt(int endereco){
        boolean topo = Boolean.parseBoolean(pilha.pop().toString());
        //pilha.push(String.valueOf(topo));
        if (topo == true){
            ip = endereco;
        }else{
            ip++;
        }
   }
   
   //1=int, 2= real, 3=literal
   private void rea(int tipo){
         try {  
             BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));  
             System.out.println("[Entrada]: ");
             String linha = "";  
             linha = reader.readLine();  
             if (tipo == this.retornaTipo(linha)){  
                 pilha.push(linha);
                 ip++;
             }else{
                 this.halt("\nRuntime error: tipo de dado invalido.");
             }
             
         }  
         catch (IOException e) {  
             System.out.println("Erro: "+ e);  
             this.halt("\n System crash in REA instruction ");
        }
   }
   
   private void wrt(){
       String bufferSaida =pilha.pop().toString();
       System.out.print("[Saida] :"+bufferSaida+"\n");
       ip++;
   }
   
   /**
    * retorno a tipo da variavel
    * 1 = valor inteiro
    * 2 = valor real
    * 3 = valor literal
    * 4 = booleano
    */
   public static int retornaTipo(Object elemento){
       String x = elemento.toString();
       int retorno = 0;
       
       try{
            Integer.parseInt(x);
            retorno = 1;
       }catch(NumberFormatException e){
            
       }
        
       try{
            if (retorno == 0){
                if (x.contains(",")){
                   x = x.replace(',','.');
                }
                Float.parseFloat(x);
                retorno = 2;
            }
            
       }catch(NumberFormatException e){
            
       }
       
       if (retorno == 0){
           if (x.equalsIgnoreCase("true") || x.equalsIgnoreCase("false")){
               retorno = 4;
           }
       }
       
       if (retorno == 0){
           retorno =3;
       }
       
       return retorno;
   
   }
   
   public void halt(String _msg){
       System.out.println(_msg);
       if (_msg==""){
            System.exit(0);
       }else{
           System.exit(666);
       }
   }
   
   private boolean testeCompatTipos(Object _obj1, Object _obj2){
       if (retornaTipo(_obj1)== retornaTipo(_obj2)){
           return true;
       }else{
           return false;
       }
   }
   
   private void executaInstrucao(String _ponteiro, String _instrucao, String _parametro){

        if (_instrucao.equalsIgnoreCase("ADD")){
            this.add();
        }
        if (_instrucao.equalsIgnoreCase("DIV")){
            this.div();
        }
        if (_instrucao.equalsIgnoreCase("MUL")){
            this.mul();
        }
        if (_instrucao.equalsIgnoreCase("SUB")){
            this.sub();
        }
        if (_instrucao.equalsIgnoreCase("ALB")){
            this.alb(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("ALI")){
            this.ali(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("ALR")){
            this.alr(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("ALS")){
            this.als(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("LDB")){
            this.ldb(_parametro);
        }
        if (_instrucao.equalsIgnoreCase("LDI")){
            this.ldi(_parametro);
        }
        if (_instrucao.equalsIgnoreCase("LDR")){
            this.ldr(_parametro);
        }
        if (_instrucao.equalsIgnoreCase("LDS")){
            this.lds(_parametro);
        }
        if (_instrucao.equalsIgnoreCase("LDV")){
           this.ldv(Integer.parseInt(_parametro)) ;
        }
        if (_instrucao.equalsIgnoreCase("STR")){
            this.str(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("AND")){
            this.and();
        }
        if (_instrucao.equalsIgnoreCase("NOT")){
            this.not();
        }
        if (_instrucao.equalsIgnoreCase("OR")){
            this.or();
        }
        if (_instrucao.equalsIgnoreCase("BGE")){
            this.bge();
        }
        if (_instrucao.equalsIgnoreCase("BGR")){
            this.bgr();
        }
        if (_instrucao.equalsIgnoreCase("DIF")){
            this.dif();
        }
        if (_instrucao.equalsIgnoreCase("EQL")){
            this.eql();
        }
        if (_instrucao.equalsIgnoreCase("SME")){
            this.sme();
        }
        if (_instrucao.equalsIgnoreCase("SMR")){
            this.smr();
        }
        if (_instrucao.equalsIgnoreCase("JMF")){
            this.jmf(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("JMP")){
            this.jmp(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("JMT")){
            this.jmt(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("STP")){
            halt("programa finalizado");
        }
        if (_instrucao.equalsIgnoreCase("REA")){
            this.rea(Integer.parseInt(_parametro));
        }
        if (_instrucao.equalsIgnoreCase("WRT")){
            this.wrt();
        }
    }
}
